#include <stdlib.h>
#include <stdio.h>
#include <GL/glut.h>

#define MAX_SPHERES 50

typedef struct {
	GLfloat x, y, z;
	int detail;
	int material;
} SphereInfo;
/* *INDENT-OFF* */

GLfloat lightPos[4] = {2.0, 4.0, 2.0, 1.0};
GLfloat lightDir[4] = {-2.0, -4.0, -2.0, 1.0};
GLfloat lightAmb[4] = {0.2, 0.2, 0.2, 1.0};
GLfloat lightDiff[4] = {0.8, 0.8, 0.8, 1.0};
GLfloat lightSpec[4] = {0.4, 0.4, 0.4, 1.0};
GLfloat matColor[3][4] = {
  {0.5, 0.0, 0.0, 1.0},
  {0.0, 0.5, 0.0, 1.0},
  {0.0, 0.0, 0.5, 1.0},
};
/* *INDENT-ON* */

GLdouble modelMatrix[16], projMatrix[16];
GLint viewport[4];
int width, height;
int opaque, transparent;
SphereInfo sphereInfo[MAX_SPHERES];
int spheres = 0;
SphereInfo overlaySphere, oldOverlaySphere;

void
drawSphere(SphereInfo * sphere)
{
	glPushMatrix();
	glTranslatef(sphere->x, sphere->y, sphere->z);
	glMaterialfv(GL_FRONT_AND_BACK,
	             GL_AMBIENT_AND_DIFFUSE, matColor[sphere->material]);
	glutSolidSphere(1.0, sphere->detail, sphere->detail);
	glPopMatrix();
}

void
display(void)
{
	int i;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	for (i = 0; i < spheres; i++) {
		drawSphere(&sphereInfo[i]);
	}
	glutSwapBuffers();
}

void
overlayDisplay(void)
{
	if (glutLayerGet(GLUT_OVERLAY_DAMAGED)) {
		/* If damaged, clear the overlay. */
		glClear(GL_COLOR_BUFFER_BIT);
	} else {
		/* If not damaged, undraw last overlay sphere. */
		glIndexi(transparent);
		drawSphere(&oldOverlaySphere);
	}
	glIndexi(opaque);
	drawSphere(&overlaySphere);
	/* Single buffered window needs flush. */
	glFlush();
	/* Remember last overaly sphere position for undrawing. */
	oldOverlaySphere = overlaySphere;
}

void
reshape(int w, int h)
{
	width = w;
	height = h;
	/* Reshape both layers. */
	glutUseLayer(GLUT_OVERLAY);
	glViewport(0, 0, w, h);
	glutUseLayer(GLUT_NORMAL);
	glViewport(0, 0, w, h);
	/* Read back viewport for gluUnProject. */
	glGetIntegerv(GL_VIEWPORT, viewport);
}

void
mouse(int button, int state, int x, int y)
{
	GLdouble objx, objy, objz;

	gluUnProject(x, height - y, 0.95,
	             modelMatrix, projMatrix, viewport,
	             &objx, &objy, &objz);
	overlaySphere.x = objx;
	overlaySphere.y = objy;
	overlaySphere.z = objz;
	overlaySphere.material = button;
	glutUseLayer(GLUT_OVERLAY);
	glutSetColor(opaque,
	             2 * matColor[button][0],  /* Red. */
	             2 * matColor[button][1],  /* Green. */
	             2 * matColor[button][2]);  /* Blue. */
	if (state == GLUT_UP) {
		glutHideOverlay();
		if (spheres < MAX_SPHERES) {
			sphereInfo[spheres] = overlaySphere;
			sphereInfo[spheres].detail = 25;  /* Fine tesselation. */
			spheres++;
		} else {
			printf("oversphere: Out of spheres.\n");
		}
		glutPostRedisplay();
	} else {
		overlaySphere.detail = 10;  /* Coarse tesselation. */
		glutShowOverlay();
		glutPostOverlayRedisplay();
	}
}

void
motion(int x, int y)
{
	GLdouble objx, objy, objz;

	gluUnProject(x, height - y, 0.95,
	             modelMatrix, projMatrix, viewport,
	             &objx, &objy, &objz);
	overlaySphere.x = objx;
	overlaySphere.y = objy;
	overlaySphere.z = objz;
	glutPostOverlayRedisplay();
}

void
setupMatrices(void)
{
	glMatrixMode(GL_PROJECTION);
	gluPerspective( /* degrees field of view */ 50.0,
	        /* aspect ratio */ 1.0, /* Z near */ 1.0, /* Z far */ 10.0);
	glMatrixMode(GL_MODELVIEW);
	gluLookAt(
	    0.0, 0.0, 5.0,      /* eye is at (0,0,5) */
	    0.0, 0.0, 0.0,      /* center is at (0,0,0) */
	    0.0, 1.0, 0.);      /* up is in positive Y direction */
}

int
main(int argc, char **argv)
{
	glutInitWindowSize(350, 350);
	glutInit(&argc, argv);

	glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
	glutCreateWindow("Overlay Sphere Positioning Demo");
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);  /* Solid spheres benefit greatly
                              from back face culling. */
	setupMatrices();
	/* Read back matrices for use by gluUnProject. */
	glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);

	/* Set up lighting. */
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
	glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmb);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiff);
	glLightfv(GL_LIGHT0, GL_SPECULAR, lightSpec);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHTING);

	glutInitDisplayMode(GLUT_INDEX | GLUT_SINGLE);
	if (glutLayerGet(GLUT_OVERLAY_POSSIBLE) == 0) {
		printf("oversphere: no overlays supported; aborting.\n");
		exit(1);
	}
	glutEstablishOverlay();
	glutHideOverlay();
	glutOverlayDisplayFunc(overlayDisplay);

	/* Find transparent and opaque index. */
	transparent = glutLayerGet(GLUT_TRANSPARENT_INDEX);
	opaque = (transparent + 1)
	         % glutGet(GLUT_WINDOW_COLORMAP_SIZE);

	/* Draw overlay sphere as an outline. */
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	/* Make sure overlay clears to transparent. */
	glClearIndex(transparent);
	/* Set up overlay matrices same as normal plane. */
	setupMatrices();

	glutMainLoop();
	return 0;
}
